package de.e2it3t.schulverwaltung;

import de.e2it3t.schulverwaltung.data.Fach;
import de.e2it3t.schulverwaltung.data.JdbcHelper;
import de.e2it3t.schulverwaltung.data.Klasse;
import de.e2it3t.schulverwaltung.data.Lehrer;
import de.e2it3t.schulverwaltung.data.Raum;
import de.e2it3t.schulverwaltung.data.Schueler;
import de.e2it3t.schulverwaltung.data.SchuelerTable;
import de.e2it3t.schulverwaltung.data.KlassenTable;
import de.e2it3t.schulverwaltung.data.KlassenUebersichtTable;
import de.e2it3t.schulverwaltung.data.Lehrblock;
import de.e2it3t.schulverwaltung.data.LehrblockTable;
import de.e2it3t.schulverwaltung.data.Lehreinheit;
import de.e2it3t.schulverwaltung.data.LehreinheitTable;
import de.e2it3t.schulverwaltung.data.SchuelerSucheTable;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.sql.Time;
import java.util.ArrayList;
import java.util.List;

/**
 * Datenbank Schnittstelle. <br>
 * <br>
 * Der Zugriff erfolgt über {@link #getInstance()} oder
 * {@link App#getDatabase()}. Beim ersten Zugriff wird die Verbindung zur
 * Datenbank aufgebaut.<br>
 * <br>
 * Diese Klasse bildet die Abstraktion für den Datenbankzugriff. Hier werden 
 * alle Datenbankzugriffe gekapselt. Die Methoden dieser Klasse operieren auf
 * POJOs und kaskadieren somit den Datenbankzugriff (Austauschbarkeit der
 * Datenbanksystems).<br>
 * <br>
 * Diese Klasse verwendet den {@link JdbcHelper}, um SQL Anweisungen zu Objekten
 * und anders herum zu konvertieren. Deshalb ist es essentiell diesen zu
 * verstehen.
 * 
 * @author Markus Kunz und Viktor Reiser
 */
public class Database {

    private static Database instance = null;
    private Connection connection;
    private JdbcHelper helper;

    
    private Database() {
        try {
            // stelle eine Datenbankverbindung zur Postgress Datenbank über den
            // JDBC Treiber her - nutze die externe Konfiguration dafür
            Class.forName("org.postgresql.Driver");
            
            Config config = App.getConfig();
            connection = DriverManager.getConnection(
                    "jdbc:postgresql://" + config.getPostgresHost() + ":"
                    + config.getPostgresPort() + "/"
                    + config.getPostgresDatabase(),
                    config.getPostgresUser(),
                    config.getPostgresPassword());
            helper = new JdbcHelper(connection);
            helper.setPrimaryDefaults("id", 0);
        } catch (Exception ex) {
            throw new RuntimeException("Failed to connect to postgres!", ex);
        }
    }

    
    /**
     * Instanz für den Datenbankzugriff. <br>
     * <br>
     * Über das Singleton Pattern wird sichergestellt, dass die Verbindung zur
     * Datenbank nur ein einziges mal aufgebaut wird.
     * 
     * @return Instanz für den Datenbankzugriff
     */
    static Database getInstance() {
        if (instance == null) {
            instance = new Database();
        }

        return instance;
    }
    
    
    public Connection getConnection() {
        return connection;
    }
    
    
    // Schüler =================================================================
    
    /**
     * Liste mit allen Schülern.
     * 
     * @return Liste mit allen Schülern
     */
    public List<Schueler> getSchueler() {
        return helper.setTableName("schueler").getList(Schueler.class);
    }
    
    /**
     * Schüler per ID.
     * 
     * @param id ID des Schülers
     * 
     * @return Schüler oder <i>null</i>, wenn es keinen Schüler mit dieser ID
     * gibt
     */
    public Schueler getSchueler(int id) {
        return helper.setTableName("schueler").get(id, Schueler.class);
    }
    
    /**
     * Liste mit allen Schülern (für die Listenansicht). <br>
     * <br>
     * Die Klasse zu der der Schüler gehört ist hier namentlich aufgelöst.<br>
     * 
     * @return Liste mit allen Schülern
     * 
     * @author Viktor Reiser
     */
    public List<SchuelerTable> getSchuelerTable() {
        return helper.setTableName("schueler_view").getList(SchuelerTable.class);
    }
    
    /**
     * Ist Schüler bereits vorhanden?
     * 
     * @param schueler zu prüfender Schüler
     * 
     * @return {@code true} wenn der Schüler mit dem gegebenen Namen bereits
     * vorhanden ist; wenn der Schüler eine desetzte ID hat, dann wird diese von
     * der Überprüfung ausgeschlossen
     */
    public boolean hasSchueler(Schueler schueler) {
        String where = "vorname = ? AND nachname = ?";
        
        if (schueler.getId() != 0) {
            where += " AND id != " + schueler.getId();
        }
        
        return helper.setTableName("schueler").setWhere(where)
                .setParameters(schueler.getVorname(), schueler.getNachname())
                .hasResults();
    }
    
    /**
     * Schüler in der Datenbank speichern
     * 
     * @param schueler zu speichernder Schüler; wenn keine ID gesetzt ist, wird
     * ein neuer angelegt, ansonsten wird der vorhande Schüler geupdatet
     */
    public void saveSchueler(Schueler schueler) {
        helper.setTableName("schueler").save(schueler);
    }
    
    /**
     * Schüler per ID löschen.
     * 
     * @param id ID des Schülers
     */
    public void deleteSchueler(int id) {
        helper.setTableName("schueler").delete(id);
    }
    
    public List<SchuelerSucheTable> sucheSchüler(String vorname, String nachname) {
        vorname = vorname == null ? "" : vorname.trim();
        nachname = nachname == null ? "" : nachname.trim();
        
        String where = "";
        List<Object> args = new ArrayList<Object>();
        
        if (!vorname.equals("")) {
            where += "vorname like ?";
            args.add("%" + vorname.toLowerCase() + "%");
        }
        if (!vorname.equals("") && !nachname.equals("")) {
            where += " and ";
        }
        if (!nachname.equals("")) {
            where += "nachname like ?";
            args.add("%" + nachname.toLowerCase() + "%");
        }
        
        return helper.setTableName("schuelersuche_view")
                .setWhere(where).setParameters(args.toArray())
                .getList(SchuelerSucheTable.class);
    }
    
    // Klassen =================================================================
    
    /**
     * Klasse per ID.
     * 
     * @param id ID der klasse
     * 
     * @return Klasse oder {@code null}, wenn keine Klasse mit dieser ID
     * vorhanden ist
     */
    public Klasse getKlasse(int id) {
        return helper.setTableName("klasse").get(id, Klasse.class);
    }
    
    /**
     * Liste mit allen Klasse.
     * 
     * @return Liste mit allen Klasse
     * 
     * @author Viktor Reiser
     */
    public List<Klasse> getKlassen() {
        return helper.setTableName("klasse").getList(Klasse.class);
    }
    
     /**
     * Liste mit allen Schülern (für die Listenansicht). <br>
     * <br>
     * Die Klasse zu der der Schüler gehört ist hier namentlich aufgelöst.<br>
     * 
     * @return Liste mit allen Schülern
     */
    public List<KlassenTable> getKlassenTable() {
        try {
            Statement stmt = connection.createStatement();
            ResultSet result = stmt.executeQuery(
                    "SELECT id, bezeichnung, klassenlehrer "
                    + "FROM klassen_view;");
            
            List<KlassenTable> klassen = new ArrayList<KlassenTable>();
            
            while (result.next()) {
                KlassenTable klasse = new KlassenTable();
                klasse.setId(result.getInt(1));
                klasse.setBezeichnung(result.getString(2));
                klasse.setKlassenlehrer(result.getString(3));
                klassen.add(klasse);
            }
            
            return klassen;
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }
    
    /**
     * Sind Klassen vorhanden?
     * 
     * @return {@code true} wenn mindestens eine Klasse vorhanden ist
     */
    public boolean hasKlassen() {
        return helper.setTableName("klasse").hasResults();
    }
     
     /**
     * Klasse in der Datenbank speichern
     * 
     * @param Klasse zu speichernder Schüler; wenn keine ID gesetzt ist, wird
     * ein neuer angelegt, ansonsten wird der vorhande Schüler geupdatet
     */
     public void saveKlasse(Klasse klasse) {
        helper.setTableName("klasse").save(klasse);
    }
     
      /**
     * Klasse per ID löschen.
     * 
     * @param id ID der Klasse
     */
    public void deleteKlasse(int id) {
        helper.setTableName("klasse").delete(id);
    }
    
     public List<KlassenUebersichtTable> getKlassenuebersicht()
     {
        return helper.setTableName("klasseuebersicht_view").getList(KlassenUebersichtTable.class);
         
     }
    
    // Lehreinheiten ===========================================================
    
    /**
     * Lehreinheit per ID.
     * 
     * @param id ID der Lehreinheit
     * 
     * @return Lehreinheit oder {@code null}, wenn es keine Lehreinheit mit
     * dieser ID gibt
     */
    public Lehreinheit getLehreinheit(int id) {
        return helper.setTableName("lehreinheit").get(id, Lehreinheit.class);
    }
    
    /**
     * Liste mit Lehreinheiten (für die Listenansicht).
     * 
     * @param klasse ID der Klasse, für welche die Lehreinheiten gelistet werden
     * sollen
     * 
     * @return Liste mit Lehreinheiten
     */
    public List<LehreinheitTable> getLehreinheitenTable(int klasse) {
        return helper.setTableName("lehreinheit_view")
                .setWhere("klasse_id = ?")
                .setParameters(klasse)
                .getList(LehreinheitTable.class);
    }
    
    /**
     * Speichere eine Lehreinheit.
     * 
     * @param lehreinheit zu speichernde Lehreinheit; wenn keine ID gesetzt ist,
     * wird eine neue angelegt, ansonsten wird die vorhande Lehreinheit
     * geupdatet
     */
    public void saveLehreinheit(Lehreinheit lehreinheit) {
        helper.setTableName("lehreinheit").save(lehreinheit);
    }
    
    /**
     * Ist eine Lehreinheit bereits vorhanden? <br>
     * <br>
     * 
     * @param lehreinheit zu prüfende Lehreinheit
     * 
     * @return {@code true} wenn bereits eine Lehreinheit für die Klasse in dem
     * Zeitraum und an dem Tag stattfindet; wenn eine ID gesetzt ist, dann wird
     * diese von der Überprüfung ausgeschlossen
     */
    public boolean hasLehreinheit(Lehreinheit lehreinheit) {
        Time beginn = lehreinheit.getUhrzeitBeginn();
        Time ende = lehreinheit.getUhrzeitEnde();
        String where = "tag = ? AND ((uhrzeit_beginn < ? AND uhrzeit_ende > ?) "
                + "OR (uhrzeit_beginn < ? AND uhrzeit_ende > ?))";
        
        if (lehreinheit.getId() != 0) {
            where += " AND id!=" + lehreinheit.getId();
        }
        
        return helper.setTableName("lehreinheit").setWhere(where)
                .setParameters(lehreinheit.getTag(), beginn, beginn, ende, ende)
                .hasResults();
    }
    
    /**
     * Lösche Lehreinheit per ID.
     * 
     * @param id ID der Lehreinheit
     */
    public void deleteLehreinheit(int id) {
        helper.setTableName("lehreinheit").delete(id);
    }
    
    
    // Räume ===================================================================
    
    /**
     * Sind Räume vorhanden?
     * 
     * @return {@code true} wenn Räume in der Datenbank vorhanden sind
     */
    public boolean hasRaeume() {
        return helper.setTableName("raum").hasResults();
    }
    
    /**
     * Liste mit allen Räumen.
     * 
     * @return Liste mit allen Räumen
     */
    public List<Raum> getRaeume() {
        return helper.setTableName("raum").getList(Raum.class);
    }
    
    
    // Fächer ==================================================================
    
    /**
     * Gibt es Fächer, welche von Lehrern unterrichtet werden?
     * 
     * @return {@code true} wenn Fächer vorhanden sind, welche von Lehrern
     * unterrichtet werden können
     */
    public boolean hasFaecherMitLehrern() {
        return helper.setTableName("fach_mit_lehrer_view").hasResults();
    }
    
    /**
     * Liste mit allen Fächern, die von Lehrern unterrichtet werden.
     * 
     * @return Liste mit allen Fächern, die von Lehrern unterrichtet werden
     */
    public List<Fach> getFaecherMitLehrern() {
        return helper.setTableName("fach_mit_lehrer_view").getList(Fach.class);
    }
    
    
    // Lehrer ==================================================================
    
    /**
     * Liste mit Lehrern welche das gegeben Fach unterrichten.
     * 
     * @param fachId ID des Fachs
     * 
     * @return Liste mit Lehrern welche das gegeben Fach unterrichten
     */
    public List<Lehrer> getLehrer(int fachId) {
        return helper.setTableName("lehrer_view")
                .setCheckColumns(false)
                .setWhere("fach_id = ?")
                .setParameters(fachId)
                .getList(Lehrer.class);
    }
    
    /**
     * Liste mit Lehrern.
     * 
     * @return Liste mit Lehrern.
     */
    public List<Lehrer> getLehrer() {
        return helper.setTableName("lehrer")
                .setCheckColumns(false)
                .getList(Lehrer.class);
    }
    
    // Lehrblöcke ==============================================================
    
    public void deleteLehrblock(int id) {
        helper.setTableName("lehrblock").delete(id);
    }
    
    public boolean hasLehrblock(Lehrblock lehrblock) {
        String where = "jahr=? and kalenderwoche=? and klasse_id=?";
        
        if (lehrblock.getId() != 0) {
            where += " AND id!=" + lehrblock.getId();
        }
        
        return helper.setTableName("lehrblock").setWhere(where)
                .setParameters(
                    lehrblock.getJahr(),
                    lehrblock.getKalenderwoche(),
                    lehrblock.getKlasseId())
                .hasResults();
    }
    
    public Lehrblock getLehrblock(int id) {
        return helper.setTableName("lehrblock").get(id, Lehrblock.class);
    }
    
    public void saveLehrblock(Lehrblock lehrblock) {
        helper.setTableName("lehrblock").save(lehrblock);
    }
    
    /**
     * Liste aller Lehrböcke (für die Listenansicht). <br>
     * <br>
     * Klassen sind namentlich aufgelöst.
     * 
     * @return 
     */
    public List<LehrblockTable> getLehrblockTable() {
        return helper.setTableName("lehrblock_view")
                .getList(LehrblockTable.class);
    }
}
